Public MustInherit Class PixelBitmapFilter
    Implements IBitmapFilter

    Dim _format As Drawing.Imaging.PixelFormat
    Dim pixels() As Byte
    Dim _locked As Boolean = False
    Dim _dataStride As Integer
    Dim _width As Integer
    Dim _height As Integer

    ''' <summary>
    ''' Applies the pixel filter to the specified bitmap, returning the new bitmap.
    ''' </summary>
    ''' <param name="orig"></param>
    ''' <returns></returns>
    ''' <remarks>This function converts the bitmap to a 32-bit ARGB bitmap.</remarks>
    Public Function ApplyFilter(ByVal orig As System.Drawing.Bitmap) As Bitmap Implements IBitmapFilter.ApplyFilter
        If orig Is Nothing Then Throw New ArgumentNullException("The bitmap cannot be null.")

        Dim bitmap As New Bitmap(orig.Width, orig.Height, Imaging.PixelFormat.Format32bppArgb)
        Graphics.FromImage(bitmap).DrawImage(orig, 0, 0, orig.Width, orig.Height)

        'Create an array of pixel data for the new bitmap
        Dim data As Imaging.BitmapData = bitmap.LockBits(New Rectangle(0, 0, bitmap.Width, bitmap.Height), Imaging.ImageLockMode.ReadWrite, Imaging.PixelFormat.Format32bppArgb)
        ReDim pixels(data.Stride * data.Height - 1)
        Runtime.InteropServices.Marshal.Copy(data.Scan0, pixels, 0, pixels.Length)

        _format = data.PixelFormat
        _dataStride = data.Stride
        _width = data.Width
        _height = data.Height
        _locked = True
        ApplyFilterLogic()
        _locked = False

        'Copy the new pixel data back into the bitmap
        Runtime.InteropServices.Marshal.Copy(pixels, 0, data.Scan0, pixels.Length)

        'Unlock the new bitmap
        bitmap.UnlockBits(data)

        Return bitmap
    End Function

    Protected ReadOnly Property ImageWidth() As Integer
        Get
            Return _width
        End Get
    End Property

    Protected ReadOnly Property ImageHeight() As Integer
        Get
            Return _height
        End Get
    End Property

    ''' <summary>
    ''' Provides logic for editing the pixels of the bitmap when overriden in a derived class.
    ''' </summary>
    ''' <remarks>This function uses the 'protected' GetPixel and SetPixel methods for working with the pixels.</remarks>
    Protected MustOverride Sub ApplyFilterLogic()

    ''' <summary>
    ''' Returns the color of the pixel at the specified coordinates.
    ''' </summary>
    ''' <param name="x"></param>
    ''' <param name="y"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Protected Function GetPixel(ByVal x As Integer, ByVal y As Integer) As Color
        If _locked Then
            Return Color.FromArgb(pixels(_dataStride * y + x * 4 + 3), pixels(_dataStride * y + x * 4 + 0), pixels(_dataStride * y + x * 4 + 1), pixels(_dataStride * y + x * 4 + 2))
        End If
    End Function

    ''' <summary>
    ''' Sets the color of the pixel at the specified coordinates.
    ''' </summary>
    ''' <param name="x"></param>
    ''' <param name="y"></param>
    ''' <param name="color"></param>
    ''' <remarks></remarks>
    Protected Sub SetPixel(ByVal x As Integer, ByVal y As Integer, ByVal color As Color)
        If _locked Then
            pixels(_dataStride * y + x * 4 + 3) = color.A
            pixels(_dataStride * y + x * 4 + 0) = color.R
            pixels(_dataStride * y + x * 4 + 1) = color.B
            pixels(_dataStride * y + x * 4 + 2) = color.G
        End If
    End Sub

End Class
